Skip to content

feat: add EVM adapter access control module#338

Open
pasevin wants to merge 31 commits intomainfrom
011-evm-access-control
Open

feat: add EVM adapter access control module#338
pasevin wants to merge 31 commits intomainfrom
011-evm-access-control

Conversation

@pasevin
Copy link
Collaborator

@pasevin pasevin commented Feb 10, 2026

Summary

Implements the full EVM Access Control module for the adapter packages, achieving 1:1 API parity with the existing Stellar adapter. This enables the Role Manager app to manage EVM contract access control through the unified AccessControlService interface.

What's included

  • @openzeppelin/ui-builder-adapter-evm-core (minor): New access-control/ module with 10 source files:

    • Capability detection — ABI analysis for Ownable, Ownable2Step, AccessControl, AccessControlEnumerable, and AccessControlDefaultAdminRules patterns
    • On-chain reads — Ownership state, admin state, role assignments, and role enumeration via viem public client
    • Transaction assembly — Ownership transfer/accept/renounce, admin transfer/accept/cancel, admin delay change/rollback, and role grant/revoke/renounce as WriteContractParameters
    • GraphQL indexer client — Historical event queries with filtering and pagination, role discovery, pending transfer queries, and grant timestamp enrichment
    • Input validation — EVM addresses and bytes32 role IDs
    • Graceful degradation — Full functionality when indexer is unavailable, returning on-chain data only
  • @openzeppelin/ui-builder-adapter-evm (minor):

    • getAccessControlService() with lazy initialization on EvmAdapter
    • accessControlIndexerUrl endpoints for all 15 EVM mainnet networks and 15 testnet networks
  • @openzeppelin/ui-builder-adapter-stellar: Migration to accessControlIndexerUrl (from indexerUri) and @openzeppelin/ui-types@1.7.0

  • apps/builder: Updated generateAndAddAppConfig to include accessControlIndexerUrl in exported app configs

Architecture

Follows the same modular structure as the Stellar adapter:

adapter-evm-core/src/access-control/
├── abis.ts              # ABI fragments for all AC functions
├── actions.ts           # Transaction assembly (WriteContractParameters)
├── constants.ts         # DEFAULT_ADMIN_ROLE, ZERO_ADDRESS
├── feature-detection.ts # ABI-based capability detection
├── index.ts             # Module exports
├── indexer-client.ts    # GraphQL client for SubQuery indexers
├── onchain-reader.ts    # viem-based on-chain reads
├── service.ts           # EvmAccessControlService (orchestrator)
├── types.ts             # EVM-specific types
└── validation.ts        # Address & role ID validation

User Stories (9 total, all complete)

# Story Priority
US1 Register contract & detect capabilities P1
US2 View ownership and admin state P1
US3 View current role assignments P1
US4 Transfer ownership (two-step) P2
US5 Transfer default admin role (two-step) P2
US6 Grant and revoke roles P2
US7 Query access control history P3
US8 Export access control snapshot P3
US9 Discover role IDs via indexer P3

Test coverage

  • 7 unit test suites in adapter-evm-core: validation, feature-detection, onchain-reader, indexer-client, actions, service (~5,900 lines of tests)
  • 1 integration test suite in adapter-evm: full adapter flow with mocked RPC/indexer
  • 1 live indexer integration test (env-var-gated via INDEXER_URL): connectivity, history queries, pagination, filtering, role discovery, pending transfers, data integrity

Spec reference

Full specification in specs/011-evm-access-control/ including plan, spec, research, data model, contract references, quickstart guide, and task tracking.

Test plan

  • All unit tests pass: pnpm --filter @openzeppelin/ui-builder-adapter-evm-core test
  • Integration test passes: pnpm --filter @openzeppelin/ui-builder-adapter-evm test
  • Both packages build: pnpm --filter @openzeppelin/ui-builder-adapter-evm-core build && pnpm --filter @openzeppelin/ui-builder-adapter-evm build
  • Live indexer integration tests pass when INDEXER_URL is set (skip gracefully when unset)
  • API parity verified against Stellar adapter (13 unified methods + EVM-specific extensions)
  • Changesets included for both packages (minor bumps)
  • CI passes on all checks

Complete design specification for the EVM adapter access control
module with 1:1 parity to the Stellar adapter. Includes spec,
plan, research, data model, API contracts, quickstart guide,
requirements checklist, and dependency-ordered tasks.
…p ui-types

Migrate Stellar adapter from generic indexerUri to the feature-specific
accessControlIndexerUrl field on BaseNetworkConfig. Update builder app
config export to prefer accessControlIndexerUrl with indexerUri fallback.
Bump @openzeppelin/ui-types to ^1.7.0 across adapter-stellar,
adapter-evm-core, adapter-evm, and builder. Add EVM-only
HistoryChangeType mappings to Stellar indexer client (mapped to UNKNOWN).
Remove temporary type augmentation files.
Update spec, quickstart, data model, and tasks to reflect
accessControlIndexerUrl migration from EvmNetworkConfig to
BaseNetworkConfig, Stellar adapter migration, and phase 0 completion.
Create shared infrastructure for the EVM access control module:
- Directory structure mirroring Stellar adapter layout
- Constants (DEFAULT_ADMIN_ROLE, ZERO_ADDRESS)
- Types (EvmAccessControlContext, EvmTransactionExecutor)
- ABI fragments for all OZ access control functions (22 ABIs)
- Feature detection signature constants for capability analysis
- T004 skipped: accessControlIndexerUrl already in ui-types@1.7.0
Add input validation for the access control module:
- validateAddress: reuses existing isValidEvmAddress, throws
  ConfigurationInvalid with contextual paramName
- validateRoleId: bytes32 hex format validation
- validateRoleIds: array validation with deduplication
- 41 tests covering addresses, role IDs, and error messages
- Update tsconfig to include test/ for IDE resolution
…on (phase 3)

Implement feature detection via ABI analysis (checking function names
AND parameter types) and the EvmAccessControlService class with
registerContract, addKnownRoleIds, getCapabilities, and dispose.
Includes 68 tests (37 feature-detection + 31 service).
…phase 4)

Implement on-chain reader (readOwnership, getAdmin via viem),
EvmIndexerClient (GraphQL queries for pending transfers), and
service methods (getOwnership, getAdminInfo) with indexer
enrichment and graceful degradation. State mapping: owned,
pending, renounced — never expired for EVM (FR-023).
…e 5)

Implement role reading and enrichment for AccessControl contracts:
- onchain-reader: hasRole, enumerateRoleMembers, readCurrentRoles,
  getRoleAdmin, getCurrentBlock
- indexer-client: queryLatestGrants for grant metadata enrichment
- service: getCurrentRoles (enumeration/knownRoleIds/hasRole fallback),
  getCurrentRolesEnriched with graceful degradation (FR-017)
- Tests for all three modules covering enumeration, known role IDs,
  DEFAULT_ADMIN_ROLE labeling, and partial enrichment failures
…phase 7)

Implement admin transfer, accept, cancel, delay change, and delay
rollback for AccessControlDefaultAdminRules contracts.

Actions module (actions.ts):
- assembleBeginAdminTransferAction
- assembleAcceptAdminTransferAction
- assembleCancelAdminTransferAction
- assembleChangeAdminDelayAction (uint48 parameter)
- assembleRollbackAdminDelayAction

Service methods (service.ts):
- transferAdminRole, acceptAdminTransfer, cancelAdminTransfer,
  changeAdminDelay, rollbackAdminDelay
- ensureHasTwoStepAdmin capability guard (FR-024)

Tests: 46 new tests (15 action + 31 service) — 386 total passing.
…hase 8)

Implement role management write operations for EVM AccessControl contracts:

- assembleGrantRoleAction, assembleRevokeRoleAction, assembleRenounceRoleAction
  in actions.ts with single-function ABI fragments
- grantRole(), revokeRole(), renounceRole() in service.ts with full input
  validation (address + bytes32 role ID) and executeTransaction delegation
- renounceRole is EVM-specific (Stellar uses revokeRole for self-revocation)
- 48 new tests covering action assembly and service method behavior

All 428 tests pass. US4+US5+US6 complete — all P2 write operations functional.
Implement historical event queries with filtering and pagination via
the indexer. Maps all 13 EVM event types to HistoryChangeType (R6).
Graceful degradation returns empty result when indexer is unavailable.
…se 10)

Implement exportSnapshot() combining getCurrentRoles() + getOwnership()
with graceful degradation for non-Ownable contracts. Validates snapshot
structure via validateSnapshot(). Adds 15 TDD tests covering all US8
acceptance scenarios, parity checks, and edge cases.
…e 11)

Implement role discovery from indexer historical events (US9):
- Add discoverRoleIds() to EvmIndexerClient with DiscoverRoles
  GraphQL query extracting unique role IDs from events
- Replace discoverKnownRoleIds() stub with full implementation:
  caching, knownRoleIds precedence, single-attempt flag
- Update attemptRoleDiscovery() to delegate to indexer discovery
- Add 21 tests covering discovery, caching, graceful degradation
Wire the EVM access control module into adapter packages:
- Create barrel exports in adapter-evm-core/src/access-control/index.ts
- Re-export access control module from adapter-evm-core/src/index.ts
- Add accessControlIndexerUrl to all mainnet and testnet network configs
- Implement lazy getAccessControlService() in EvmAdapter with
  executeTransaction callback wrapping signAndBroadcast
- Add integration test covering lazy init, service interface,
  full register-capabilities-ownership flow, and callback wiring
Add minor changesets for adapter-evm-core (access control module) and
adapter-evm (getAccessControlService + indexer URLs). Mark Phase 13
tasks complete: test suite validation, quickstart verification, TODO
review, and API parity check.
Add env-var-gated integration tests for the EVM indexer client
against real SubQuery indexers on Sepolia. Tests cover connectivity,
history queries with filtering/pagination, role discovery, latest
grants, pending transfers, data integrity, and contract-specific
verification for AccessControlMock, OwnableMock, Ownable2StepMock,
and CombinedMock contracts. All tests skip gracefully when
INDEXER_URL is not set.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an EVM Access Control module (in adapter-evm-core) and wires it into the EVM adapter via a lazily initialized getAccessControlService(), while also migrating Stellar + Builder to the new accessControlIndexerUrl network config field and bumping @openzeppelin/ui-types to ^1.7.0.

Changes:

  • Add EVM access-control implementation (capability detection, on-chain reads, tx assembly, indexer client, and service orchestration) with extensive tests and an adapter integration test.
  • Migrate Stellar adapter and Builder export logic to prefer accessControlIndexerUrl (with fallback to indexerUri) and update network configs accordingly.
  • Bump @openzeppelin/ui-types dependencies/locks and add changesets for EVM packages.

Reviewed changes

Copilot reviewed 50 out of 52 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
specs/011-evm-access-control/spec.md New spec describing EVM access control module requirements and parity targets.
specs/011-evm-access-control/research.md Research decisions (viem, GraphQL indexer, type mapping).
specs/011-evm-access-control/quickstart.md Implementation guide + cross-repo prerequisites.
specs/011-evm-access-control/plan.md Implementation plan and structure.
specs/011-evm-access-control/data-model.md Data model and unified type mappings.
specs/011-evm-access-control/contracts/indexer-queries.graphql GraphQL query contracts used by the indexer client.
specs/011-evm-access-control/contracts/feature-detection.ts ABI signature matrix contract for capability detection.
specs/011-evm-access-control/contracts/access-control-service.ts API contract/reference for the service methods.
specs/011-evm-access-control/checklists/requirements.md Requirements quality checklist and cross-repo alignment notes.
pnpm-lock.yaml Lockfile updates for @openzeppelin/ui-types@1.7.0.
packages/adapter-stellar/test/access-control/service.test.ts Update tests to use accessControlIndexerUrl.
packages/adapter-stellar/test/access-control/ownable-two-step.test.ts Update tests to use accessControlIndexerUrl.
packages/adapter-stellar/test/access-control/indexer-integration.test.ts Update integration test config to use accessControlIndexerUrl.
packages/adapter-stellar/test/access-control/indexer-client.test.ts Update tests to use accessControlIndexerUrl.
packages/adapter-stellar/test/access-control/indexer-client.spec.ts Update tests to use accessControlIndexerUrl.
packages/adapter-stellar/test/access-control/admin-two-step.test.ts Update tests to use accessControlIndexerUrl.
packages/adapter-stellar/src/networks/testnet.ts Rename indexerUriaccessControlIndexerUrl for Stellar testnet.
packages/adapter-stellar/src/networks/mainnet.ts Rename indexerUriaccessControlIndexerUrl for Stellar mainnet.
packages/adapter-stellar/src/access-control/indexer-client.ts Prefer accessControlIndexerUrl ?? indexerUri; add mapping entries for new change types.
packages/adapter-stellar/package.json Bump @openzeppelin/ui-types to ^1.7.0.
packages/adapter-evm/test/access-control-integration.test.ts New integration test covering adapter ↔ service wiring and lazy init.
packages/adapter-evm/src/networks/testnet.ts Add accessControlIndexerUrl to defined EVM testnet configs.
packages/adapter-evm/src/networks/mainnet.ts Add accessControlIndexerUrl to defined EVM mainnet configs.
packages/adapter-evm/src/adapter.ts Add lazy getAccessControlService() that wraps signAndBroadcast.
packages/adapter-evm/package.json Bump @openzeppelin/ui-types to ^1.7.0.
packages/adapter-evm-core/tsconfig.json Include test/**/* in TS typecheck scope.
packages/adapter-evm-core/test/access-control/validation.test.ts New validation tests for EVM addresses + bytes32 roles.
packages/adapter-evm-core/test/access-control/onchain-reader.test.ts New tests for on-chain ownership/admin/roles readers.
packages/adapter-evm-core/test/access-control/feature-detection.test.ts New tests for ABI-based capability detection.
packages/adapter-evm-core/test/access-control/actions.test.ts New tests for tx assembly helpers.
packages/adapter-evm-core/src/index.ts Export new access-control module public API.
packages/adapter-evm-core/src/access-control/validation.ts Add throwing validators for addresses + bytes32 roles.
packages/adapter-evm-core/src/access-control/types.ts Add internal context and tx executor types.
packages/adapter-evm-core/src/access-control/onchain-reader.ts Add viem-based on-chain reads for ownership/admin/roles.
packages/adapter-evm-core/src/access-control/index.ts Barrel export for access-control module.
packages/adapter-evm-core/src/access-control/feature-detection.ts Add ABI signature matching for supported access-control patterns.
packages/adapter-evm-core/src/access-control/constants.ts Add DEFAULT_ADMIN_ROLE and ZERO_ADDRESS constants.
packages/adapter-evm-core/src/access-control/actions.ts Add action assemblers returning WriteContractParameters.
packages/adapter-evm-core/src/access-control/abis.ts Add single-function ABI fragments + detection signature constants.
packages/adapter-evm-core/package.json Bump @openzeppelin/ui-types to ^1.7.0.
apps/builder/src/export/versions.ts Update exported app dependency versions (ui-types to 1.7.0).
apps/builder/src/export/assemblers/generateAndAddAppConfig.ts Prefer accessControlIndexerUrl ?? indexerUri when generating exported app configs.
apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap Snapshot updates for ui-types version bump.
apps/builder/package.json Bump @openzeppelin/ui-types to ^1.7.0.
.changeset/evm-access-control-core.md Changeset for adapter-evm-core access control module feature.
.changeset/evm-access-control-adapter.md Changeset for adapter-evm service integration + network indexer URLs.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 18 to 20
iconComponent: NetworkStellar,
indexerUri: 'https://openzepplin-stellar-testnet.graphql.subquery.network',
accessControlIndexerUrl: 'https://openzepplin-stellar-testnet.graphql.subquery.network',
};
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Stellar testnet indexer URL appears to contain a typo in the subdomain (openzepplin vs openzeppelin). If this is not an intentional endpoint, it will break history/indexer features on Stellar testnet.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +9
- Implement `getAccessControlService()` with lazy initialization on EvmAdapter
- Add `accessControlIndexerUrl` endpoints for all EVM mainnet networks (Ethereum, Polygon, Arbitrum, Optimism, Base, Avalanche, BSC, Gnosis, Celo, Scroll, ZKsync, Linea, Blast, Mantle, Mode)
- Add `accessControlIndexerUrl` endpoints for all EVM testnet networks (Sepolia, Amoy, Arbitrum Sepolia, Optimism Sepolia, Base Sepolia, Fuji, BSC Testnet, Chiado, Alfajores, Scroll Sepolia, ZKsync Sepolia, Linea Sepolia, Blast Sepolia, Mantle Sepolia, Mode Sepolia)
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changeset claims accessControlIndexerUrl endpoints were added for networks like Gnosis, Celo, Blast, Mantle, and Mode, but the updated packages/adapter-evm/src/networks/mainnet.ts and testnet.ts in this PR only define a smaller subset of networks. Please either add the missing network configs/endpoints or adjust the release note to match what’s actually included.

Copilot uses AI. Check for mistakes.
Comment on lines +203 to +213
// Return cached capabilities if available
if (context.capabilities !== null) {
logger.debug(
'EvmAccessControlService.getCapabilities',
`Returning cached capabilities for ${context.contractAddress}`
);
return context.capabilities;
}

// Check indexer availability based on configuration
const indexerAvailable = this.hasIndexerEndpoint();
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCapabilities() sets supportsHistory based only on whether an indexer URL is configured (hasIndexerEndpoint()), not whether the indexer is actually reachable. This can incorrectly report supportsHistory: true when the endpoint is down, contradicting the intended semantics. Consider awaiting this.indexerClient.isAvailable() (and optionally refreshing cached capabilities when availability changes).

Suggested change
// Return cached capabilities if available
if (context.capabilities !== null) {
logger.debug(
'EvmAccessControlService.getCapabilities',
`Returning cached capabilities for ${context.contractAddress}`
);
return context.capabilities;
}
// Check indexer availability based on configuration
const indexerAvailable = this.hasIndexerEndpoint();
// Helper to determine current indexer availability based on configuration and runtime health.
const getIndexerAvailability = async (): Promise<boolean> => {
// If there is no configured endpoint, history is not supported.
if (!this.hasIndexerEndpoint()) {
return false;
}
// If an indexer client has not been created, conservatively report unavailable.
if (!this.indexerClient) {
return false;
}
try {
return await this.indexerClient.isAvailable();
} catch (error) {
logger.warn(
'EvmAccessControlService.getCapabilities',
'Failed to verify indexer availability; treating as unavailable',
{ error }
);
return false;
}
};
// If capabilities are cached, refresh supportsHistory based on current indexer availability.
if (context.capabilities !== null) {
const indexerAvailable = await getIndexerAvailability();
if (context.capabilities.supportsHistory === indexerAvailable) {
logger.debug(
'EvmAccessControlService.getCapabilities',
`Returning cached capabilities for ${context.contractAddress}`
);
return context.capabilities;
}
const updatedCapabilities: AccessControlCapabilities = {
...context.capabilities,
supportsHistory: indexerAvailable,
};
context.capabilities = updatedCapabilities;
logger.debug(
'EvmAccessControlService.getCapabilities',
`Updated cached capabilities for ${context.contractAddress} based on indexer availability change`
);
return updatedCapabilities;
}
const indexerAvailable = await getIndexerAvailability();

Copilot uses AI. Check for mistakes.
Comment on lines +1360 to +1363
* Uses the config precedence: `accessControlIndexerUrl` on the network config.
*/
private hasIndexerEndpoint(): boolean {
return !!this.networkConfig.accessControlIndexerUrl;
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasIndexerEndpoint() only checks networkConfig.accessControlIndexerUrl. For backward compatibility and to match the documented precedence, this should also fall back to networkConfig.indexerUri (and/or the same resolution logic used by the indexer client), otherwise networks/configs that still set only indexerUri will be treated as having no indexer configured.

Suggested change
* Uses the config precedence: `accessControlIndexerUrl` on the network config.
*/
private hasIndexerEndpoint(): boolean {
return !!this.networkConfig.accessControlIndexerUrl;
* Uses the config precedence: `accessControlIndexerUrl` on the network config,
* falling back to `indexerUri` for backward compatibility.
*/
private hasIndexerEndpoint(): boolean {
return !!(this.networkConfig.accessControlIndexerUrl ?? this.networkConfig.indexerUri);

Copilot uses AI. Check for mistakes.

constructor(networkConfig: EvmCompatibleNetworkConfig) {
this.networkConfig = networkConfig;
this.endpoint = networkConfig.accessControlIndexerUrl;
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indexer endpoint is resolved only from networkConfig.accessControlIndexerUrl. If callers still provide indexerUri (or you want to preserve the same precedence as the Stellar adapter), this should prefer accessControlIndexerUrl but fall back to indexerUri to avoid silently disabling history on older configs.

Suggested change
this.endpoint = networkConfig.accessControlIndexerUrl;
this.endpoint = networkConfig.accessControlIndexerUrl ?? networkConfig.indexerUri;

Copilot uses AI. Check for mistakes.
validateRoleId(roleIds[i], `${paramName}[${i}]`);
}

return [...new Set(roleIds.map((r) => r.trim()))];
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateRoleIds() deduplicates using the trimmed string but does not normalize case. Since bytes32 hex strings are case-insensitive, role IDs that differ only by casing will not be deduplicated, which can cause redundant queries and inconsistent downstream behavior (e.g., DEFAULT_ADMIN_ROLE labeling). Consider normalizing to a consistent case (typically lowercase) before deduplication/return.

Suggested change
return [...new Set(roleIds.map((r) => r.trim()))];
const normalizedRoleIds = roleIds.map((r) => r.trim().toLowerCase());
return [...new Set(normalizedRoleIds)];

Copilot uses AI. Check for mistakes.
…ry events

Replace plain string 'OWNER' fallback with event-type-aware role
resolution in transformToHistoryEntries. Ownership events now use
DEFAULT_ADMIN_ROLE (bytes32 zero) with label 'OWNER', admin events
use DEFAULT_ADMIN_ROLE with label 'DEFAULT_ADMIN_ROLE', ensuring
role.id is always a valid bytes32 hex string in the EVM context.
validateRoleId trimmed whitespace internally but returned void,
causing callers (grantRole, revokeRole, renounceRole) to pass
untrimmed role IDs to transaction assembly. Now returns the
trimmed string, consistent with validateRoleIds.
queryLatestGrants keyed the grant map by account address only,
causing cross-role metadata contamination when an account holds
multiple roles. Switch to a composite role:account key via the
new grantMapKey() helper and update getCurrentRolesEnriched to
use the same composite lookup.
… control

- Well-known role dictionary and resolveRoleLabel(); ABI-based role discovery
- addKnownRoleIds() accepts { id, label } pairs; roleLabelMap in context
- Thread roleLabelMap through readCurrentRoles(), queryHistory(), resolveRoleFromEvent()
- Add constants, role-discovery, and service tests for label resolution
…scovery

- Guard ensureAbiRoleLabels with has() check so addKnownRoleIds labels win
- Batch role discovery RPC calls with Promise.allSettled for lower latency
- Simplify hash normalization in discoverRoleLabelsFromAbi
- Add test: external label survives conflicting ABI discovery result
Consolidate three independent createPublicClient call sites into a
single createEvmPublicClient utility in utils/public-client.ts.
Access control service was using networkConfig.rpcUrl directly,
bypassing the RPC resolution priority (user config > app override >
default). All 4 on-chain read call sites now use resolveRpcUrl().
…sitive dedup

validateRoleId() now lowercases hex after validation, ensuring Set-based
deduplication and Map lookups are case-insensitive. validateRoleIds()
reuses validated output instead of raw trim for consistency.
…ites

Add 20 new tests for human-readable role label resolution:
- onchain-reader: roleLabelMap resolution, well-known fallback, precedence
- indexer-client: label propagation through queryHistory events
- role-discovery: on-chain constant calls, graceful failure, normalization
- service: label threading through getCurrentRoles, getHistory, exportSnapshot
- indexer-integration: real indexer label resolution with well-known and
  custom labels, ownership sentinel handling, role discovery labeling

Fix two pre-existing integration test assumptions:
- Use changeType sentinel check instead of label presence for role filtering
- Use composite role:account keys for grantMap lookups
…ings tab

Add resolveAccessControlIndexerUrl following the resolveRpcUrl pattern to
support user-configured indexer URL overrides via UserNetworkServiceConfigService.
Wire the resolver into EvmIndexerClient and add an Access Control Indexer tab
to the EVM network settings dialog with validation and connection testing.
…le contracts

For contracts without AccessControlEnumerable, on-chain reads cannot
enumerate who holds each role. Both getCurrentRoles() and
getCurrentRolesEnriched() now query the indexer's roleMemberships
to populate member arrays when on-chain enumeration is unavailable.
…tests

Cover getCurrentRoles and getCurrentRolesEnriched behavior for
non-enumerable contracts that rely on indexer-based member discovery:
- populate members from indexer grants for non-enumerable contracts
- leave members empty when indexer is unavailable
- leave members empty when indexer query fails
- skip indexer for enumerable contracts with existing members
- enriched variant with grant metadata from indexer
- enriched fallback when indexer unavailable or query fails
…ice methods

Validate hasOwnable before getOwnership() and hasTwoStepAdmin before
getAdminInfo() to prevent confusing on-chain reverts when called on
contracts missing the required interfaces.
…ce methods

Validate hasOwnable before getOwnership() and hasTwoStepAdmin before
getAdminInfo()/getAdminAccount() to prevent confusing on-chain errors
when called on contracts missing the required interfaces. Checks are
soft — skipped when the contract is not registered to preserve backward
compatibility.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant